# asterisk-to-proxy.pm
# Copyright (C) 2006  Stephane Alnet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 

# This file is part of the CarrierClass.net project.
# For more information: http://carrierclass.net/
#
package CCNv2::Provision::Asterisk::to_proxy;
use strict;
use warnings;
use base qw(CCNCore::Provision::Base);
use CCNv2::Parameters;
use CCN::Text;
use CCNCore::Scripting;

sub run()
{
    our $self = shift;
    return unless defined $self->p->{cp_member};

    my $default_proxy = $self->sql_get(q(SELECT default_outbound_px_cluster FROM cluster WHERE pk = ?),$self->p->{cp_member});
    return $self->error ("No default outbound proxy cluster specified for call-processing cluster ".$self->p->{cp_member})
        unless defined $default_proxy;

    # ------------- dispatch -------------

    my $global_toproxy = template_text <<'EOT',
[macro-dispatch] ; extension [, preferred_proxy]
exten => s,1,Set(OUTBOUND_PX=${ARG2})
exten => s,2,GotoIf(${OUTBOUND_PX}?4)
exten => s,3,Set(OUTBOUND_PX=_{default_proxy}_)
exten => s,4,Goto(dispatch,${ARG1},1)
;
[dispatch]
; call to an asterisk-controlled trunk
exten => _TTr.,1,Goto(trunk-to-proxy,${EXTEN},1)
; call to an IP phone (without treatment: this goes directly to the device)
; (NOTE: this is used to route both e{ENTEPRISE}m{NUMBER} AND full logins from within [route-elin])
exten => _e.,1,GotoIf(${POOL}?2:3)
exten => _e.,2,Goto(pool-${POOL}-to-proxy,${EXTEN},1)
exten => _e.,3,Macro(send-to-proxy-_{default_proxy}_,${EXTEN},1)
exten => _e.,4,Return
; call to the emergency router
exten => _ERl.,1,Goto(enterprise-${ENTERPRISE}-to-proxy,${EXTEN},1)
; call to a phone (with treatment)
exten => _PHo.,1,Goto(enterprise-${ENTERPRISE}-to-proxy,${EXTEN},1)
; call to voicemail
exten => _VM.,1,Goto(enterprise-${ENTERPRISE}-to-proxy,${EXTEN},1)
; call to a third-party gateway
exten => _TGw.,1,Macro(send-to-proxy-_{default_proxy}_,${EXTEN},1)
; other targets are invalid
exten => i,1,Noop(ERROR: Invalid dispatch destination ${EXTEN})
exten => s,1,Noop(ERROR: Invalid dispatch destination 's')
EOT
        {
            'default_proxy' => ${default_proxy},
        };

    # ---------- trunk-to-proxy -------------

    $global_toproxy .= "[trunk-to-proxy]\n";

    # Local trunks: route internally.
    my $TIDPAT = CCNv2::Parameters::trunk_pattern;

    $self->sql_do_for( <<'SQL', $self->p->server_pk, $self->p->{cp_member},
        SELECT pk FROM server_trunks WHERE server = ?
        UNION
        SELECT pk FROM gw_trunks WHERE cp_cluster = ?
SQL
    sub
    {
        my $TRUNK = CCNv2::Parameters::trunk_id(shift);
        $global_toproxy .= <<"EOT";
exten => _TTr${TRUNK}m.,1,Goto(from-proxy,\${EXTEN},1)
EOT
    });

    # Remote trunks, select a proxy (pool-based, then default)
    $global_toproxy .= <<"EOT";
exten => _TTr${TIDPAT}m.,1,GotoIf(\${POOL}?2)
exten => _TTr${TIDPAT}m.,2,Goto(pool-\${POOL}-to-proxy,\${EXTEN},1)
exten => _TTr${TIDPAT}m.,3,Macro(send-to-proxy-${default_proxy},\${EXTEN},1)
EOT

    # ----------- TO PROXY -----------------

    # Can this list be made shorter?
    # A list of proxies used by pools + a list of proxies used by trunks
    # + the default proxy + the voicemail proxy + .. ?
    $self->sql_do_for( <<'SQL', $self->p->{cp_member}, $self->p->{cp_member}, $self->p->{cp_member}, $self->p->{cp_member}, $self->p->server_pk,
        SELECT px_cluster FROM cluster WHERE pk = ?
        UNION
        SELECT default_outbound_px_cluster FROM cluster WHERE pk = ?
        UNION
        SELECT px_cluster FROM pool WHERE cp_cluster = ?
        UNION
        SELECT px_cluster FROM gw_trunks WHERE cp_cluster = ?
        UNION
        SELECT px_cluster FROM server_trunks WHERE server = ?
SQL
    sub
    {
        my $CLUSTER = shift;
        # Route an INVITE to a proxy.
        # Since Asterisk does not support SRV, use a list of Dial() statements. (Yuck.)
        $global_toproxy .= <<"EOT";
[macro-send-to-proxy-${CLUSTER}] ; target
exten => s,1,Goto(s-1,1)
;
exten => s-CLEANEXIT,1,Return
exten => s-ANSWER,1,Goto(s-CLEANEXIT,1)
exten => s-BUSY,1,Goto(s-CLEANEXIT,1)
; exten => s-NOANSWER,1,Goto(s-CLEANEXIT,1)
exten => s-CANCEL,1,Goto(s-CLEANEXIT,1)
;
; Progress
;
exten => s-PROGRESS,1,Goto(s-\${NEXT},3)
exten => s-CHANUNAVAIL,1,Goto(s-PROGRESS,1)
exten => s-CONGESTION,1,Goto(s-PROGRESS,1)
exten => s-NOANSWER,1,Goto(s-PROGRESS,1)
; We have to progress on NOANSWER because OpenSER may reply with a 100, but the
; far-end may be unresponsive. Ideally OpenSER should be taking care of it --
; meaning the list of far-end gateways to trakc down should be in an SRV that
; OpenSER uses to try them -- not from within Asterisk.
;
; TransferStatus
exten => s-SUCCESS,1,Goto(s-CLEANEXIT,1)
exten => s-FAILURE,1,Goto(s-\${CURRENT},22)
exten => s-UNSUPPORTED,1,Goto(s-\${CURRENT},22)
EOT
        my $n = 1;

        $self->sql_do_for( <<'SQL', $CLUSTER,
            SELECT pk FROM server WHERE px_member = ?
SQL
        sub
        {
            my $PROXY_pk = shift;
            my $next = $n+1;

            my $PROXY_NAME = $self$self->p->make_server_name($PROXY_pk,'px');

            $global_toproxy .= <<"EOT";
exten => s-$n,1,Set(TO_PROXY_OK=1)
exten => s-$n,2,SIPAddHeader(X-CCN-Media-Proxy-Already-Engaged: \${SIP_HEADER(X-CCN-Media-Proxy-Engaged)})
exten => s-$n,3,Set(NEXT=$next)
exten => s-$n,4,Set(CURRENT=$n)
exten => s-$n,5,GotoIf(\${DIAL_TIMEOUT}?10:20)
;
exten => s-$n,10,Dial(SIP/\${ARG1}\@${PROXY_NAME},\${DIAL_TIMEOUT})
exten => s-$n,11,Goto(s-\${DIALSTATUS},1)
;
exten => s-$n,20,Transfer(SIP/\${ARG1}\@${PROXY_NAME}:0)
;; exten => s-$n,21,Goto(s-\${TRANSFERSTATUS},1)
;; exten => s-$n,22,Dial(SIP/\${ARG1}\@${PROXY_NAME})
;; exten => s-$n,23,Goto(s-\${DIALSTATUS},1)
EOT
            $n++;
        }); # foreach @servers

        # All proxies have been tried. :(
        $global_toproxy .= <<"EOT";
exten => s-$n,1,Set(NEXT=)
exten => s-$n,2,Set(TO_PROXY_OK=0)
exten => s-$n,3,Return
EOT
    });


    for("/etc/asterisk/config/extensions.dispatch.conf")
    {
        CAT $_, $global_toproxy;
        IF_CHANGED $_ => CCNv2::Scripting::AST('extensions reload');
    }


    sub pools_to_proxy()
    {
        my $pools_to_proxy = '';

        $self->sql_do_for( <<'SQL', $self->p->{cp_member},
            SELECT enterprise, pk, px_cluster FROM pool WHERE cp_cluster = ?
SQL
        sub
        {
            my $ENTERPRISE = shift;

            # ----------- POOL-to-proxy -----------------

            # Calls from phones are routed per-pool using that pool's proxy cluster
            my $POOL = shift;
            my $PROXY = shift;
            return $self->error ("Pool $POOL has no proxy assigned to itself.")
                unless defined $PROXY;
            $pools_to_proxy .= "[pool-${POOL}-to-proxy]\n".
                "exten => _[a-zA-Z0-9*#].,1,Macro(send-to-proxy-${PROXY},\${EXTEN})\n";
        }); # foreach @pools

        for("/etc/asterisk/config/extensions.pools-to-proxy.conf")
        {
            CAT $_, $pools_to_proxy;
            IF_CHANGED $_ => CCNv2::Scripting::AST('extensions reload');
        }
    }
    pools_to_proxy();

    # ----------- ENTERPRISE-to-proxy -----------------


    $self->sql_do_for( <<'SQL', $self->p->{cp_member},
        SELECT DISTINCT enterprise FROM pool WHERE cp_cluster = ?
SQL
    sub
    {
        my $ENTERPRISE_pk = shift;
        my $ENTERPRISE_id = CCNv2::Parameters::enterprise_id($ENTERPRISE_pk);

        my $toproxy = "[enterprise-${ENTERPRISE_pk}-to-proxy]\n";

        # These get resolved by aliases.
        # Note: By nature (see phones.pm) these are _always_ off-device, so there's no point in trying to optimize the routing here.
        #       (They will always be routed to another call-processing cluster.)
        $toproxy .= <<"EOT";
exten => _PHo${ENTERPRISE_id}m.,1,Macro(send-to-proxy-\${OUTBOUND_PX},\${EXTEN})
EOT

        # YYY We probably don't need all patterns -- only the ones for extensions we serve, or for menus we have (...??)
        $self->sql_do_for( <<'SQL', $ENTERPRISE_pk,
            SELECT pattern, cp_cluster FROM voicemail WHERE enterprise = ?
SQL
        sub
        {
            my $PATTERN = shift;
            my $target = shift;
            my $target_name = $self->p->make_cluster_name($target,'vm');

            $self->error ("No voicemail target defined for enterprise $ENTERPRISE_pk, pattern $PATTERN"),
            next if not defined $target;

            # Local voicemail: route internally.
            if( $target eq $self->p->{vm_member} )
            {
                $toproxy .= <<"EOT";
exten => _VMb${ENTERPRISE_id}m${PATTERN},1,Goto(from-proxy,\${EXTEN},1)
exten => _VMo${ENTERPRISE_id}m${PATTERN},1,Goto(from-proxy,\${EXTEN},1)
exten => _VMu${ENTERPRISE_id}m${PATTERN},1,Goto(from-proxy,\${EXTEN},1)
exten => _VMm${ENTERPRISE_id}m${PATTERN},1,Goto(from-proxy,\${EXTEN},1)
exten => _VMs${ENTERPRISE_id}m${PATTERN},1,Goto(from-proxy,\${EXTEN},1)
EOT
            }
            # Other voicemail clusters: help the SIP proxy.
            else
            {
                $toproxy .= <<"EOT";
exten => _VMb${ENTERPRISE_id}m${PATTERN},1,Macro(send-to-proxy-\${OUTBOUND_PX},\${EXTEN}/${target_name})
exten => _VMo${ENTERPRISE_id}m${PATTERN},1,Macro(send-to-proxy-\${OUTBOUND_PX},\${EXTEN}/${target_name})
exten => _VMu${ENTERPRISE_id}m${PATTERN},1,Macro(send-to-proxy-\${OUTBOUND_PX},\${EXTEN}/${target_name})
exten => _VMm${ENTERPRISE_id}m${PATTERN},1,Macro(send-to-proxy-\${OUTBOUND_PX},\${EXTEN}/${target_name})
exten => _VMs${ENTERPRISE_id}m${PATTERN},1,Macro(send-to-proxy-\${OUTBOUND_PX},\${EXTEN}/${target_name})
EOT
            }
        }); # foreach @patterns

        # YYY Probably optimizable too
        $self->sql_do_for( <<'SQL', $ENTERPRISE_pk,
            SELECT "pk", "number", "erouter" FROM erl WHERE enterprise = ?
SQL
        sub
        {
            my $ERL_pk = shift;
            my $ERL_number = shift;
            my $target = shift;
            my $target_name = $self->p->make_cluster_name($target,'er');

            # Route locally
            if( $target eq $self->p->{cp_member} )
            {
                $toproxy .= <<"EOT";
exten => _ERl${ENTERPRISE_pk}l${ERL_number},1,Goto(from-proxy,\${EXTEN},1)
EOT
                $self->sql_do_for( <<'SQL', $ERL_pk,
                    SELECT "number" FROM elin WHERE erl = ?
SQL
                sub
                {
                    my $ELIN = shift;
                    $toproxy .= <<"EOT";
exten => _ELi${ENTERPRISE_id}k${ELIN},1,Goto(from-proxy,\${EXTEN},1)
EOT
                });
            }
            # Route through the proxy
            else
            {
                $toproxy .= <<"EOT";
exten => _ERl${ENTERPRISE_pk}l${ERL_number},1,Macro(send-to-proxy-\${OUTBOUND_PX},\${EXTEN}/${target_name})
EOT
                $self->sql_do_for( <<'SQL', $ERL_pk,
                    SELECT "number" FROM elin WHERE erl = ?
SQL
                sub
                {
                    my $ELIN = shift;
                    $toproxy .= <<"EOT";
exten => _ELi${ENTERPRISE_id}k${ELIN},1,Macro(send-to-proxy-\${OUTBOUND_PX},\${EXTEN}/${target_name})
EOT
                });
            }
        });

        for("/etc/asterisk/config/clients/${ENTERPRISE_pk}/extensions.to-proxy.conf")
        {
            CAT $_, $toproxy;
            IF_CHANGED $_ => CCNv2::Scripting::AST('extensions reload');
        }
    }); # foreach @cp_enterprise
}
1;